package com.lyn.plugin;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.os.Build;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;

import androidx.appcompat.app.AppCompatActivity;

import com.lyn.plugin.annotation.PluginLauncher;
import com.lyn.plugin.entity.Plugin;
import com.lyn.plugin.entity.PluginInfo;
import com.lyn.plugin.entity.PluginParameter;
import com.lyn.plugin.utils.AdbUtl;
import com.lyn.plugin.utils.LogUtil;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import dalvik.system.DexClassLoader;
import dalvik.system.DexFile;

public class PluginHelper {

    /**
     * 缓存resources
     */
    public final static Map<String, Resources> resourcesMap = new ConcurrentHashMap<>();

    /**
     * 加载插件
     */
    public static Plugin load(Context context, File apkFile) {
        try {
            //合并插件dex
            mergeDex(context, apkFile.getAbsolutePath(), null);
            Resources resources = getResources(context, apkFile.getAbsolutePath());
            //提取插件so
            File soFileFolder = extractSo(apkFile);
            loadMultipleSo(soFileFolder, context.getClassLoader());
            //获取插件启动类className和插件名称
            PluginInfo pluginInfo = getPluginInfo(apkFile);
            String pluginClassName = pluginInfo.getClassName();
            String pluginName = pluginInfo.getName();
            Plugin plugin = new Plugin();
            plugin.setApkPath(apkFile.getAbsolutePath());
            plugin.setClassName(pluginClassName);
            plugin.setName(pluginName);
            assert soFileFolder != null;
            plugin.setSoPath(soFileFolder.getAbsolutePath());
            plugin.setResources(resources);
            plugin.setLogoId(pluginInfo.getLogoId());
            return plugin;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 合并dex
     *
     * @param context           上下文
     * @param apkPath           插件路径
     * @param librarySearchPath so路径
     * @throws Exception
     */
    private static void mergeDex(Context context, String apkPath, String librarySearchPath) throws Exception {
        Class<?> dexPathListClass = Class.forName("dalvik.system.DexPathList");
        Class<?> baseDexClassLoader = Class.forName("dalvik.system.BaseDexClassLoader");
        Field pathListField = baseDexClassLoader.getDeclaredField("pathList");
        pathListField.setAccessible(true);
        Field dexElementsField = dexPathListClass.getDeclaredField("dexElements");
        dexElementsField.setAccessible(true);
        //获取宿主dex的类加载器
        ClassLoader hostClassLoader = context.getClassLoader();
        Object hostPathList = pathListField.get(hostClassLoader);
        //获取宿主dexElements数组
        Object[] hostEexElements = (Object[]) dexElementsField.get(hostPathList);
        //获取插件dex的类加载器
        ClassLoader pluginClassLoader = new DexClassLoader(apkPath,
                context.getCacheDir().getAbsolutePath()
                , librarySearchPath
                , hostClassLoader);
        //获取插件dexElements数组
        Object pluginPathList = pathListField.get(pluginClassLoader);
        Object[] pluginEexElements = (Object[]) dexElementsField.get(pluginPathList);
        //创建新的dexElements数组
        Object[] newDexElements = (Object[]) Array.newInstance(Objects.requireNonNull(hostEexElements.getClass().getComponentType())
                , hostEexElements.length + pluginEexElements.length);
        System.arraycopy(hostEexElements, 0, newDexElements, 0, hostEexElements.length);
        System.arraycopy(pluginEexElements, 0, newDexElements, hostEexElements.length, pluginEexElements.length);
        //把旧的dexElements数组替换新的
        dexElementsField.set(hostPathList, newDexElements);
    }


    /**
     * 提取插件第一启动activity
     *
     * @param apkFile 插件apk文件
     * @return className
     * @throws Exception
     */
    public static PluginInfo getPluginInfo(File apkFile) throws Exception {
        DexFile dexFile = new DexFile(apkFile);
        PluginInfo pluginInfo = null;
        Enumeration<String> entries = dexFile.entries();
        while (entries.hasMoreElements()) {
            String className = entries.nextElement();
            if (!className.startsWith("com")) {
                continue;
            }
            Class<?> aClass = Class.forName(className);
            if (aClass.isAnnotationPresent(PluginLauncher.class)) {
                pluginInfo = new PluginInfo();
                pluginInfo.setClassName(className);
                PluginLauncher pluginLauncher = aClass.getAnnotation(PluginLauncher.class);
                if (pluginLauncher != null) {
                    pluginInfo.setName(pluginLauncher.pluginName());
                    pluginInfo.setLogoId(pluginLauncher.logoId());
                }
            }
        }
        return pluginInfo;
    }


    /**
     * 提取apk so 到 lib文件夹
     *
     * @param apkFile 插件文件
     * @return 返回提取lib 文件夹文件
     * @throws Exception 异常
     */
    public static File extractSo(File apkFile) throws Exception {
        if (!apkFile.exists()) {
            return null;
        }
        String outPath = apkFile.getParent();
        File libFolderFile = new File(outPath, "lib");
        if (!libFolderFile.exists()) {
            libFolderFile.mkdirs();
        }
        String cpuAbi = AdbUtl.getCPUAbi().trim();
        ZipFile zipFile = new ZipFile(apkFile.getAbsoluteFile());
        Enumeration<? extends ZipEntry> entries = zipFile.entries();
        while (entries.hasMoreElements()) {
            ZipEntry zipEntry = entries.nextElement();
            String name = zipEntry.getName().trim();
            if (!name.startsWith("lib")) {
                continue;
            }
            if (!name.contains(cpuAbi)) {
                continue;
            }
            LogUtil.e(name);
            String[] split = name.split("/");
            InputStream inputStream = zipFile.getInputStream(zipEntry);
            File soFile = new File(libFolderFile.getAbsoluteFile(), split[split.length - 1]);
            if (!soFile.exists()) {
                soFile.createNewFile();
            }
            FileOutputStream outputStream = new FileOutputStream(soFile);
            byte[] data = new byte[1024];
            int length;
            while ((length = inputStream.read(data)) != -1) {
                outputStream.write(data, 0, length);
            }
            inputStream.close();
            outputStream.flush();
            outputStream.close();
        }
        return libFolderFile;
    }


    public static void loadMultipleSo(File soFileFolder, ClassLoader classLoader) {
        File[] files = soFileFolder.listFiles();
        List<File> successList = new LinkedList<>();
        List<File> failList = new LinkedList<>();
        for (File file : files) {
            if (TextUtils.isEmpty(loadSingleSo(file.getAbsolutePath(), classLoader))) {
                successList.add(file);
            } else {
                failList.add(file);
            }
        }
        if (failList.size()<1){
            return;
        }
        for (File file:failList){
            if (TextUtils.isEmpty(loadSingleSo(file.getAbsolutePath(), classLoader))) {
                successList.add(file);
                failList.remove(file);
            }
        }
        if (failList.size()<1){
            return;
        }
        for (File file:failList){
            if (TextUtils.isEmpty(loadSingleSo(file.getAbsolutePath(), classLoader))) {
                successList.add(file);
                failList.remove(file);
            }
        }
    }

    /**
     * 执行加载so库
     *
     * @param path        so库文件路径
     * @param classLoader 当前调用类加载器
     * @return 加载结果 返回null代表成功
     */
    public static String loadSingleSo(String path, ClassLoader classLoader) {
        try {
            Runtime runtime = Runtime.getRuntime();
            if (Build.VERSION.SDK_INT > 27) {
                @SuppressLint("DiscouragedPrivateApi")
                Method nativeLoadMethod = runtime.getClass().getDeclaredMethod("nativeLoad"
                        , String.class, ClassLoader.class);
                nativeLoadMethod.setAccessible(true);
                return (String) nativeLoadMethod.invoke(runtime, path, classLoader);
            }
            @SuppressLint("DiscouragedPrivateApi")
            Method nativeLoadMethod = runtime.getClass().getDeclaredMethod("nativeLoad"
                    , String.class, ClassLoader.class, String.class);
            nativeLoadMethod.setAccessible(true);
            return (String) nativeLoadMethod.invoke(runtime, path, classLoader, null);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 加载插件资源
     */
    public static Resources getResources(Context context, String apkPath) {
        if (TextUtils.isEmpty(apkPath)) {
            return null;
        }
        Resources resources = resourcesMap.get(apkPath);
        if (resources != null) {
            return resources;
        }
        //创建AssetManager对象
        AssetManager assetManager;
        try {
            assetManager = AssetManager.class.newInstance();
            //加载apkAssetManager
            Class<? extends AssetManager> assetManagerClass = assetManager.getClass();
            Method addAssetPath = assetManagerClass.getMethod("addAssetPath", String.class);
            addAssetPath.setAccessible(true);
            addAssetPath.invoke(assetManager, apkPath);
            Resources currResources = context.getResources();
            Resources pluginResources = new Resources(assetManager, currResources.getDisplayMetrics(), currResources.getConfiguration());
            resourcesMap.put(apkPath, pluginResources);
            return pluginResources;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 启动插件activity
     */
    public static void startActivity(AppCompatActivity activity, Plugin plugin) {
        Intent intent = new Intent(activity, ProxyActivity.class);
        intent.putExtra(ProxyActivity.ACTIVITY_CLASS_NAME, plugin.getClassName());
        intent.putExtra(ProxyActivity.PLUGIN_PATH, plugin.getApkPath());
        activity.startActivity(intent);
    }


    /**
     * 设置activity视图View
     */
    public static View getView(PluginParameter pluginParameter, int id) {
        XmlResourceParser layout = pluginParameter.getResources().getLayout(id);
        return LayoutInflater.from(pluginParameter.getActivity()).inflate(layout, null, false);
    }


}
